﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections.Concurrent;

namespace Sparrow.Reflection
{
    public class PropertyMetadata : MemberMetadata
    {
        public PropertyMetadata(PropertyInfo propertyInfo)
        {
            
            this.Name = propertyInfo.Name;
      
            this.Attributes = new List<AttributeMetadata>();
            foreach (var attr in propertyInfo.GetCustomAttributes(false))
            {
                this.Attributes.Add(new AttributeMetadata { Attribute = attr });
            }

            //copy some code from FastReflectionLib (http://fastreflectionlib.codeplex.com/)

            //complie getter
            if (propertyInfo.CanRead)
            {


                // Target: (object)(((TInstance)instance).Property)

                // preparing parameter, object type
                var instance = Expression.Parameter(typeof(object), "instance");

                // non-instance for static method, or ((TInstance)instance)
                var instanceCast = propertyInfo.GetGetMethod(true).IsStatic ? null :
                    Expression.Convert(instance, propertyInfo.ReflectedType);

                // ((TInstance)instance).Property
                var propertyAccess = Expression.Property(instanceCast, propertyInfo);

                // (object)(((TInstance)instance).Property)
                var castPropertyValue = Expression.Convert(propertyAccess, typeof(object));

                // Lambda expression
                var lambda = Expression.Lambda<Func<object, object>>(castPropertyValue, instance);

                //var lambda = Expression.Lambda<Func<object, dynamic>>(propertyAccess, instance);

                this.Getter = lambda.Compile();
            }
            else
            {
                this.Getter = this.EmptyGetter;

            }

            //complie setter

            if (propertyInfo.CanWrite)
            {
                // Target: ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)

                var methodInfo = propertyInfo.GetSetMethod(true);

                // parameters to execute
                var instanceParameter = Expression.Parameter(typeof(object), "instance");
                var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");

                // build parameter list
                var parameterExpressions = new List<Expression>();
                var paramInfos = methodInfo.GetParameters();
                for (int i = 0; i < paramInfos.Length; i++)
                {
                    // (Ti)parameters[i]
                    BinaryExpression valueObj = Expression.ArrayIndex(
                        parametersParameter, Expression.Constant(i));
                    UnaryExpression valueCast = Expression.Convert(
                        valueObj, paramInfos[i].ParameterType);

                    parameterExpressions.Add(valueCast);
                }

                // non-instance for static method, or ((TInstance)instance)
                var instanceCast = methodInfo.IsStatic ? null :
                    Expression.Convert(instanceParameter, methodInfo.ReflectedType);

                // static invoke or ((TInstance)instance).Method
                var methodCall = Expression.Call(instanceCast, methodInfo, parameterExpressions);

                // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
                if (methodCall.Type == typeof(void))
                {
                    var lambda = Expression.Lambda<Action<object, object[]>>(
                            methodCall, instanceParameter, parametersParameter);

                    Action<object, object[]> execute = lambda.Compile();
                    this.Setter = (instance, parameters) =>
                    {
                        execute(instance, parameters);
                        return null;
                    };
                }
                else
                {
                    var castMethodCall = Expression.Convert(methodCall, typeof(object));
                    var lambda = Expression.Lambda<Func<object, object[], object>>(
                        castMethodCall, instanceParameter, parametersParameter);

                    this.Setter = lambda.Compile();
                }
            }
            else
            {
                this.Setter = EmptySetter;
            }



        }

        object EmptySetter(object instance, object[] parameters)
        {
            return null;
        }

        object EmptyGetter(object instance)
        {
            return null;
        }


        Func<object, object> Getter
        {
            get;
            set;
        }

        Func<object, object[], object> Setter
        {
            get;
            set;
        }

        public object GetValue(object instance)
        {
            return this.Getter(instance);
        }

        public void SetValue(object instance, params object[] parameters)
        {
            this.Setter(instance, parameters);
        }


    }
}
